package org.orbroker

import org.orbroker.exception._
import java.sql.{ SQLException, ResultSet, Connection, PreparedStatement }

/**
 * The session encapsulating the a connection
 * from the data source.
 * @author Nils Kilden-Pedersen
 */
private[orbroker] abstract class Session(
    isolationLevel: Option[Int],
    broker: Broker,
    private val extConn: Option[Connection]) {
  private var conn: Connection = null
  
  protected def commit() = if (conn != null) conn.commit()
  protected def rollback() = if (conn != null) conn.rollback()

  val alwaysPrepare = broker.alwaysPrepare

  def getStatement(token: Token[_]) = token.getStatement(broker)
  def getModStatement(token: Token[_]) = token.getStatement(broker).asInstanceOf[ModifyStatement]
  def getCallStatement(token: Token[_]) = token.getStatement(broker).asInstanceOf[CallStatement]

  val callback = broker.callback
  protected val readOnly: Boolean
  protected def hasUncommittedChanges: Boolean

  /** 
   * Timeout in seconds. Will cause a [[TimeoutException]]
   * if an execution takes longer than the given time. 
   * 0 means no limit.
   * @see java.sql.Statement#setQueryTimeout(int)
   */
  var timeout = broker.timeout

  /** 
   * Fetch size in rows.
   * @see java.sql.Statement#setFetchSize(int)
   */
  var fetchSize = broker.fetchSize

  protected[orbroker] final def connection = extConn match {
    case Some(ec) ⇒ ec
    case None ⇒ {
      if (conn == null) {
        conn = broker.newConnection(isolationLevel)
        if (conn.isReadOnly != readOnly) conn.setReadOnly(readOnly)
      }
      conn
    }
  }

  private[orbroker] def discardConnection() {
    if (conn != null) {
      try { conn.close() } catch { case _ ⇒ /* Ignore */ }
      conn = null
    }
  }

  private[orbroker] def close() {
    if (conn != null) {
      try {
        conn.rollback() // Rollback for drivers that require it before closing
        if (hasUncommittedChanges) {
          throw new RollbackException
        }
      } finally {
        import JdbcCloser._
        checkWarningsAndClose(conn, broker.callback)
      }
    }
  }

  protected final def toMap(args: Iterable[(String, _)]): Map[String, Any] = {
    var map: Map[String, Any] = broker.defaultParms
    for (arg ← args)
      map += arg
    map
  }

  protected final def evaluate(e: SQLException) = {
    if (broker.adapter.isConstraint(e))
      new ConstraintException(e, broker.adapter.constraintName(e))
    else if (broker.adapter.isTimeout(e))
      new TimeoutException(e)
    else if (broker.adapter.isDeadlock(e))
      new DeadlockException(e)
    else
      e
  }

  protected final def diffTimeInMicros(startNanos: Long): Int = ((System.nanoTime - startNanos) / 1000L).asInstanceOf[Int]
}
